/**
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

package pittypat.web;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import pittypat.IJson;
import pittypat.Permission;

/**
 * Pittypat 自定义的 HttpServlet。
 * <p>
 * ApiServlet 用于处理来自客户端的 AJAX 请求，WebSocket 请求将由 WebSocketEndPoint 负责处理。 不需要在
 * web.xml 中配置 ApiServlet，该Servlet 由 ApiContextListener.contextInitialized
 * 内部完成添加和路径映射。
 * <p>
 * ApiServlet 仅接受 POST 和 OPTIONS 请求，所有其他请求都将发送 405 (Method Not Allowed) 代码。
 */
public final class ApiServlet extends HttpServlet {
	private static final long serialVersionUID = -6437323820295434076L;
	private static final Logger LOGGER = LogManager.getLogger(ApiServlet.class);

	/**
	 * 初始化 ApiServlet 类的新实例。
	 */
	public ApiServlet() {
		super();
	}

	/**
	 * 检查当前是否正在尝试进行 WebSocket 连接。
	 * 
	 * @param request
	 * @return 一个布尔值，指示当前是否正在创建 WebSocket 连接。
	 */
	private boolean isWebSocketRequest(HttpServletRequest request) {
		return "GET".equalsIgnoreCase(request.getMethod())
				&& "Upgrade".equalsIgnoreCase(request.getHeader("Connection"))
				&& "websocket".equalsIgnoreCase(request.getHeader("Upgrade"));
	}

	/**
	 * 处理指定的请求，并对客户端进行响应。
	 * 
	 * @throws IOException
	 * @throws ServletException
	 * 
	 */
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) {
		try {
			if (isWebSocketRequest(request)) {
				// WebSocket 请求应该由容器负责转交给 WebSocketEndPoint
				response.sendError(412, "Precondition Failed");
				return;
			}

			// 请求上下文
			RequestContext context = RequestContext.setupForServlet(request);

			// 获取请求路径，第一个元素是去掉 appPath 后的完整路径，其他分别为：开发者代码、App 标识符、模块标识符和功能标识符。
			// 如果是 /user/* 或 /notification/*，则返回的数组只有三个元素
			String[] pathParts = context.getPathParts();

			if (pathParts == null || pathParts.length == 0) {
				// 无效的请求路径。
				response.sendError(400, "Bad Request");
				return;
			}

			if ("user".equalsIgnoreCase(pathParts[1])) {
				// 与用户相关的请求
				UserHandler.handle(pathParts[0], request, response);
			} else if ("notification".equalsIgnoreCase(pathParts[1])) {
				// 执行与通知相关的请求处理
				NotificationHandler.handle(pathParts[0], request, response);
			} else {
				// 获取请求对应的功能信息
				FunctionInfo info = context.getFuncInfo();
				if (info == null) {
					// 没有找到目标功能
					response.sendError(404, "Not Found");
					return;
				}

				if (info.moduleInfo.isMaintenance) {
					// 指示目标模块正在维护当中
					ResponseHelper.sendErrorMessage(response,
							"目标功能 " + pathParts[0] + " 正在维护当中，暂时不可访问：\r\n"
									+ (info.moduleInfo.maintenanceMessage == null
											|| info.moduleInfo.maintenanceMessage.length() == 0 ? "维护结束后将恢复正常，请稍后再试。"
													: info.moduleInfo.maintenanceMessage));

					// 已经发送错误消息，终止继续处理
					return;
				}

				// 检查用户的权限
				Permission permission = context.getPermission();
				if (!permission.isAuthorized()) {
					// 没有通过权限检查
					if (permission.getUser().getId() != null) {
						// 非匿名用户
						ResponseHelper.sendErrorMessage(response,
								"当前用户没有权限访问 " + pathParts[0] + "。\r\n请询问系统管理人员您的权限，必要时可由管理员为您分配该权限。");
					} else {
						// 匿名用户
						ResponseHelper.sendErrorMessage(response,
								"用户未登录或会话已经过期，没有权限访问 " + pathParts[0] + "。\r\n请重新登录。");
					}

					// 已经发送错误消息，终止继续处理
					return;
				}

				// doPost
				super.service(request, response);
			}
		} catch (Exception exc) {
			// 在请求处理过程中发生异常
			LOGGER.error("处理请求 '" + request.getRequestURI() + "' 时发生错误：\r\n" + exc.getLocalizedMessage(), exc);
			ResponseHelper.sendErrorMessage(response, exc, ApiContextListener.isDebug());
		}
	}

	/**
	 * 处理 POST 请求。
	 * 
	 * @throws IOException
	 * @throws ServletException
	 * 
	 */
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		try {
			// 已经通过 service 保存在请求中的功能信息
			FunctionInfo info = RequestContext.getRequestContext(request).getFuncInfo();
			// 调用目标功能的 API 方法
			IJson result = info.invoke(request.getParameterMap() /* query */, null /* form */,
					WebAsyncProgress.DEFAULT);

			if (info.getModel().isSuccess()) {
				// 功能 API 执行成功，发送方法返回的 IJson 结果
				ResponseHelper.sendValueMessage(response, result);
			} else {
				// 功能 API 执行失败，发送错误消息，该消息可能来自于数据库的 _success 参数
				ResponseHelper.sendErrorMessage(response, info.getModel().getMessage());
			}
		} catch (Throwable exc) {
			if (exc instanceof InvocationTargetException) {
				Throwable t = ((InvocationTargetException) exc).getCause();
				if (t != null) {
					exc = t;
				}
			}

			// 在处理请求过程中发生错误
			LOGGER.error("处理请求 '" + request.getRequestURI() + "' 时发生错误：" + exc.getLocalizedMessage(), exc);
			ResponseHelper.sendErrorMessage(response, exc, ApiContextListener.isDebug());
		}
	}

	/**
	 * 处理 GET 请求。总是发送 405 (Method Not Allowed) 错误代码。
	 * 
	 * @throws IOException
	 * @throws ServletException
	 * 
	 */
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.sendError(405, "Method Not Allowed");
	}

	/**
	 * 处理 DELETE 请求。总是发送 405 (Method Not Allowed) 错误代码。
	 * 
	 * @throws IOException
	 * @throws ServletException
	 * 
	 */
	@Override
	protected void doDelete(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.sendError(405, "Method Not Allowed");
	}

	/**
	 * 处理 HEAD 请求。总是发送 405 (Method Not Allowed) 错误代码。
	 * 
	 * @throws IOException
	 * @throws ServletException
	 * 
	 */
	@Override
	protected void doHead(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.sendError(405, "Method Not Allowed");
	}

	/**
	 * 处理 OPTIONS 请求。
	 * 
	 * @throws IOException
	 * @throws ServletException
	 * 
	 */
	@Override
	protected void doOptions(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.setHeader("Allow", "GET,POST,OPTIONS");
		response.setStatus(200);
	}

	/**
	 * 处理 PUT 请求。总是发送 405 (Method Not Allowed) 错误代码。
	 * 
	 * @throws IOException
	 * @throws ServletException
	 * 
	 */
	@Override
	protected void doPut(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.sendError(405, "Method Not Allowed");
	}

	/**
	 * 处理 TRACE 请求。总是发送 405 (Method Not Allowed) 错误代码。
	 * 
	 * @throws IOException
	 * @throws ServletException
	 * 
	 */
	@Override
	protected void doTrace(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.sendError(405, "Method Not Allowed");
	}
}
